#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>
#include <unistd.h>

#include "../au.h"
#include "../common.h"
#include "afile.h"

enum {
	A_NULL,
	A_SIZE,
	A_ENC,
	A_RATE,
	A_CHAN
};

enum {
	AF_FORM,
	AF_CHUNK_HDR,
	AF_CHUNK_BODY,
	AF_COMM_BODY,
	AF_SSND_BODY,
	AF_SSND_OFFSET,
	AF_SSND_DATA,
	AF_SSND_DONE,
	AF_EOF
};

struct afile {
	uint8_t		 buffer[26];
	size_t		 index;
	size_t		 sample;
	size_t		 byte;
	uint32_t	 encoding;
	uint32_t	 form_size;
	uint32_t	 chunk_size;
	uint32_t	 sample_size;
	int		 bswap;
	int		 state;
	int		 fd;
	int		 aifc;
	int		 comm;
	uint16_t	 comm_channels;
	uint32_t	 comm_samples;
	uint16_t	 comm_width;
	uint32_t	 comm_rate;
	int		 ssnd;
	uint32_t	 ssnd_offset;
	uint32_t	 ssnd_block;
	off_t		 ssnd_data;
};

const struct a_lookup stab[] = {
	{ "size", A_SIZE },
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "rate", A_RATE },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ NULL, A_NULL }
};

static uint32_t	getrate(const void *);
static uint16_t	get2(const void *);

struct afile *
af_open(const char *file)
{
	struct afile *af;
	int fd;

	if (file == NULL)
		ERR(EINVAL, NULL);
	if ((fd = open(file, O_RDONLY)) < 0)
		return (NULL);
	if ((af = af_fdopen(fd)) == NULL)
		(void)close(fd);
	return (af);
}

struct afile *
af_fdopen(int fd)
{
	struct afile *a;

	if (fd < 0)
		ERR(EINVAL, NULL);
	if ((a = malloc(sizeof(struct afile))) == NULL)
		return (NULL);
	a->index = 0;
	a->byte = 0;
	a->sample = 0;
	a->encoding = 0;
	a->bswap = 0;
	a->state = AF_FORM;
	a->fd = fd;
	a->comm = 0;
	a->ssnd = 0;
	return (a);
}

void
af_close(struct afile *a)
{

	if (a == NULL)
		return;
	(void)close(a->fd);
	free(a);
}

int
af_get(struct afile *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	if (!a->comm)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_SIZE:
			*va_arg(ap, uint32_t *) = a->comm_samples *
			    a->sample_size;
			break;
		case A_ENC:
			*va_arg(ap, uint32_t *) = a->encoding;
			break;
		case A_RATE:
			*va_arg(ap, uint32_t *) = a->comm_rate;
			break;
		case A_CHAN:
			*va_arg(ap, uint32_t *) = a->comm_channels;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
af_set(struct afile *a, const char *s, ...)
{

	ERR(EOPNOTSUPP, -1);
}

ssize_t
af_read(struct afile *a, void *b, size_t s)
{
	ssize_t r;

	if (a == NULL && b == NULL)
		ERR(EFAULT, -1);
	for (;;)
		switch (a->state) {
		case AF_FORM:
			if ((r = nread(a->fd, a->buffer + a->index,
			    12 - a->index)) <= 0)
				return (r);
			if ((a->index += r) >= 12) {
				if (memcmp(a->buffer + 0, "FORM", 4)
				    != 0)
					ERR(EINVAL, -1);
				if (memcmp(a->buffer + 8, "AIFF", 4)
				    == 0)
					a->aifc = 0;
				else if (memcmp(a->buffer + 8, "AIFC",
				    4) == 0)
					a->aifc = 1;
				else
					ERR(EINVAL, -1);
				a->index = 0;
				a->byte += 12;
				a->form_size = get4(a->buffer + 4);
				if (a->form_size < 42 +
				    (a->aifc ? 8 : 0))
					a->state = AF_EOF;
				else
					a->state = AF_CHUNK_HDR;
			}
			break;
		case AF_CHUNK_HDR:
			if ((r = nread(a->fd, a->buffer + a->index,
			    8 - a->index)) <= 0)
				return (r);
			if ((a->index += r) >= 8) {
				a->index = 0;
				a->chunk_size = get4(a->buffer + 4);
				if (a->chunk_size > a->form_size ||
				    a->form_size - a->chunk_size
				    < a->byte) {
					a->state = AF_EOF;
					return (0);
				}
				a->byte += a->chunk_size;
				if (memcmp(a->buffer, "COMM", 4)
				    == 0) {
					if (a->comm)
						ERR(EINVAL, -1);
					if (a->chunk_size < 18)
						ERR(EINVAL, -1);
					a->state = AF_COMM_BODY;
				} else if (memcmp(a->buffer, "SSND", 4)
				    == 0) {
					if (a->ssnd)
						ERR(EINVAL, -1);
					a->state = AF_SSND_BODY;
				} else
					a->state = AF_CHUNK_BODY;
			}
			break;
		case AF_CHUNK_BODY:
			if ((r = nread(a->fd, b,
			    MIN(s, a->chunk_size - a->index))) <= 0)
				return (r);
			if ((a->index += r) >= a->chunk_size) {
				a->index = 0;
				a->state = AF_CHUNK_HDR;
			}
			break;
		case AF_COMM_BODY:
			if (a->index < sizeof(a->buffer))
				r = nread(a->fd, a->buffer + a->index,
				    MIN(a->chunk_size,
				    sizeof(a->buffer)) - a->index);
			else
				r = nread(a->fd, b, a->chunk_size -
				    a->index);
			if (r <= 0)
				return (r);
			if ((a->index += r) >= a->chunk_size) {
				a->index = 0;
				a->comm = 1;
				a->comm_channels = get2(a->buffer + 0);
				a->comm_samples = get4(a->buffer + 2);
				a->comm_width = get2(a->buffer + 6);
				a->sample_size =
				    (uint32_t)((a->comm_width + 7) / 8)
				    * (uint32_t)a->comm_channels;
				if ((a->comm_rate =
				    getrate(a->buffer + 8)) == 0)
					ERR(EINVAL, -1);
				if (a->aifc) {
					if (memcmp(a->buffer + 18,
					    "sowt", 4) == 0)
						a->bswap = 1;
					else if (memcmp(a->buffer + 18,
					    "fl32", 4) == 0 ||
					    memcmp(a->buffer + 18,
					    "FL32", 4) == 0)
						a->encoding =
						    AU_FLOAT32;
					else if (memcmp(a->buffer + 18,
					    "fl64", 4) == 0 ||
					    memcmp(a->buffer + 18,
					    "FL64", 4) == 0)
						a->encoding =
						    AU_FLOAT64;
					else if (memcmp(a->buffer + 18,
					    "ulaw", 4) == 0 ||
					    memcmp(a->buffer + 18,
					    "ULAW", 4) == 0)
						a->encoding = AU_ULAW;
					else if (memcmp(a->buffer + 18,
					    "alaw", 4) == 0 ||
					    memcmp(a->buffer + 18,
					    "ALAW", 4) == 0)
						a->encoding = AU_ALAW;
					else if (memcmp(a->buffer + 18,
					    "NONE", 4) != 0)
						ERR(EINVAL, -1);
				}
				if (a->encoding == 0) {
					if (a->comm_width <= 8)
						a->encoding = AU_PCM8;
					else if (a->comm_width <= 16)
						a->encoding = AU_PCM16;
					else if (a->comm_width <= 24)
						a->encoding = AU_PCM24;
					else if (a->comm_width <= 32)
						a->encoding = AU_PCM32;
					else
						ERR(EINVAL, -1);
				}
				a->state = AF_CHUNK_HDR;
				if (a->comm_samples > UINT32_MAX /
				    a->sample_size || a->comm_rate == 0)
					ERR(ERANGE, -1);
			}
			break;
		case AF_SSND_BODY:
			if ((r = nread(a->fd, a->buffer + a->index,
			    8 - a->index)) <= 0)
				return (r);
			if ((a->index += r) >= 8) {
				a->ssnd = 1;
				a->ssnd_offset = get4(a->buffer + 0);
				a->ssnd_block = get4(a->buffer + 4);
				a->index = 0;
				if (a->ssnd_offset > a->chunk_size ||
				    (a->chunk_size - a->ssnd_offset) /
				    a->sample_size < a->comm_samples)
					a->state = AF_EOF;
				else if (a->ssnd_offset == 0) {
					a->ssnd_data = lseek(a->fd, 0,
					    SEEK_CUR);
					a->state = AF_SSND_DATA;
				} else
					a->state = AF_SSND_OFFSET;
			}
			break;
		case AF_SSND_OFFSET:
			if ((r = nread(a->fd, b,
			    MIN(s, a->ssnd_offset - a->index))) <= 0)
				return (r);
			if ((a->index += r) >= a->ssnd_offset) {
				a->ssnd_data = lseek(a->fd, 0,
				    SEEK_CUR);
				a->state = AF_SSND_DATA;
			}
			break;
		case AF_SSND_DATA:
			s -= s % a->sample_size;
			if ((r = nread(a->fd, b,
			    MIN(s, a->chunk_size - a->index))) <= 0)
				return (r);
			if ((a->index += r) >= a->chunk_size)
				a->state = AF_SSND_DONE;
			a->sample += r / a->sample_size;
			if (a->bswap)
				(void)au_encbswap(a->encoding, b, r);
			return (r);
		case AF_SSND_DONE:
			/* FALLTHROUGH */
		case AF_EOF:
			return (0);
		}
	/* NOTREACHED */
}

struct timespec
af_seek(struct afile *a, struct timespec ts, int whence)
{
	const struct timespec tserr = { -1, -1 };
	int64_t s;
	off_t t;

	if (a == NULL)
		ERR(EINVAL, tserr);
	if (a->ssnd_data <= 0)
		ERR(EOPNOTSUPP, tserr);
	s = ts.tv_sec * (int64_t)a->comm_rate + ts.tv_nsec *
	    INT64_C(1000000000) / (int64_t)a->comm_rate;
	switch (whence) {
	case SEEK_SET:
		break;
	case SEEK_END:
		s = (int64_t)a->comm_samples - s;
		break;
	case SEEK_CUR:
		s += a->sample;
		break;
	default:
		ERR(EINVAL, tserr);
	}
	if (s < 0)
		s = 0;
	else if (s > a->comm_samples)
		s = a->comm_samples;
	if (s != a->sample) {
		if ((t = lseek(a->fd, (off_t)a->ssnd_data +
		    s * (off_t)a->sample_size, SEEK_SET)) < 0)
			return (tserr);
		s = (t - a->ssnd_data) / a->sample_size;
		a->sample = s;
	}
	ts.tv_sec = s / a->comm_rate;
	ts.tv_nsec = (s % a->comm_rate) * INT64_C(1000000000) /
	    a->comm_rate;
	return (ts);
}

struct timespec
af_length(struct afile *a)
{
	const struct timespec tserr = { -1, -1 };
	struct timespec ts;

	if (a == NULL)
		ERR(EINVAL, tserr);
	if (!a->ssnd)
		ERR(EOPNOTSUPP, tserr);
	ts.tv_sec = a->comm_samples / a->comm_rate;
	ts.tv_nsec = (uint64_t)(a->comm_samples % a->comm_rate) *
	    UINT64_C(1000000000) / (uint64_t)a->comm_rate;
	return (ts);
}

uint32_t
getrate(const void *b)
{
	const uint8_t *ub = b;
	uint64_t v;
	uint32_t e;

	assert(b != NULL);
	e = get2(b);
	v = ((uint64_t)ub[2] << 56) | ((uint64_t)ub[3] << 48) |
	    ((uint64_t)ub[4] << 40) | ((uint64_t)ub[5] << 32) |
	    ((uint64_t)ub[6] << 24) | ((uint64_t)ub[7] << 16) |
	    ((uint64_t)ub[8] << 8) | (uint64_t)ub[9];
	if (e >> 15 || e < 0x3FFF)
		return (0);
	e -= 0x3FFF;
	if (e > 63 + 32)
		ERR(ERANGE, 0); /* too big */
	if (e < 63) {
		e = 63 - e;
		if ((UINT64_MAX >> e) & v)
			ERR(ERANGE, 0); /* not an integer */
		v >>= e;
	} else if (e > 63) {
		e -= 63;
		if ((UINT64_MAX << e) & v)
			ERR(ERANGE, 0); /* too big */
		v <<= e;
	}
	if (v > UINT32_MAX)
		ERR(ERANGE, 0);
	return (v);
}

uint16_t
get2(const void *b)
{

	assert(b != NULL);
	return (((uint16_t)((const uint8_t *)b)[0] << 8) |
	    (uint16_t)((const uint8_t *)b)[1]);
}
